package state

import (
	"fmt"
)

type State interface {
	insertQuarter()
	ejectQuarter()
	turnCrank()
	dispense()
}

type SoldOutState struct {
	gMachine *GumballMachine
}

func NewSoldOutState(g *GumballMachine) *SoldOutState {
	r := new(SoldOutState)
	r.gMachine = g

	return r
}

func (s *SoldOutState) insertQuarter() {
	fmt.Println("You can't insert a quarter, the machine is sold out")
}

func (s *SoldOutState) ejectQuarter() {
	fmt.Println("You can't eject, you haven't inserted a quarter yet")
}

func (s *SoldOutState) turnCrank() {
	fmt.Println("You turned, but there are no gumballs")
}

func (s *SoldOutState) dispense() {
	fmt.Println("No gumball dispensed")
}

type NoQuarterState struct {
	gMachine *GumballMachine
}

func NewNoQuarterState(g *GumballMachine) *NoQuarterState {
	r := new(NoQuarterState)
	r.gMachine = g

	return r
}

func (s *NoQuarterState) insertQuarter() {
	fmt.Println("You inserted a quarter")
	s.gMachine.setState(s.gMachine.getHasQuarterState())
}

func (s *NoQuarterState) ejectQuarter() {
	fmt.Println("You haven't inserted a quarter")
}

func (s *NoQuarterState) turnCrank() {
	fmt.Println("You turned, but there's no quarter")
}

func (s *NoQuarterState) dispense() {
	fmt.Println("You need to pay first")
}

type HasQuarterState struct {
	gMachine *GumballMachine
}

func NewHasNoQuarterState(g *GumballMachine) *HasQuarterState {
	r := new(HasQuarterState)
	r.gMachine = g

	return r
}

func (s *HasQuarterState) insertQuarter() {
	fmt.Println("You can't insert another quarter")
}

func (s *HasQuarterState) ejectQuarter() {
	fmt.Println("Quarter returned")
	s.gMachine.setState(s.gMachine.getNoQuarterState())
}

func (s *HasQuarterState) turnCrank() {
	fmt.Println("You turned...")
	s.gMachine.setState(s.gMachine.getSoldState())
}

func (s *HasQuarterState) dispense() {
	fmt.Println("No gumball dispensed")
}

type SoldState struct {
	gMachine *GumballMachine
}

func NewSoldState(g *GumballMachine) *SoldState {
	r := new(SoldState)
	r.gMachine = g

	return r
}

func (s *SoldState) insertQuarter() {
	fmt.Println("Please wait, we're already giving you a gumball")
}

func (s *SoldState) ejectQuarter() {
	fmt.Println("Sorry, you already turned the crank")
}

func (s *SoldState) turnCrank() {
	fmt.Println("Turning twice doesn't get you another gumball!")
}

func (s *SoldState) dispense() {
	s.gMachine.releaseBall()
	if s.gMachine.getCount() > 0 {
		s.gMachine.setState(s.gMachine.getNoQuarterState())
	} else {
		fmt.Println("Oops, out of gumballs!")
		s.gMachine.setState(s.gMachine.getSoldOutState())
	}
}

/////////////////////////////////////////////////////////////////

type GumballMachine struct {
	count           int
	soldOutState    State
	noQuarterState  State
	hasQuarterState State
	soldState       State

	curState State
}

func NewGumballMachine(n int) *GumballMachine {
	r := new(GumballMachine)
	r.count = n
	r.soldOutState = NewSoldOutState(r)
	r.noQuarterState = NewNoQuarterState(r)
	r.hasQuarterState = NewHasNoQuarterState(r)
	r.soldState = NewSoldState(r)

	if n > 0 {
		r.curState = r.noQuarterState
	}

	return r
}

func (s *GumballMachine) InsertQuarter() {
	s.curState.insertQuarter()
}

func (s *GumballMachine) EjectQuarter() {
	s.curState.ejectQuarter()
}

func (s *GumballMachine) TurnCrank() {
	s.curState.turnCrank()
	s.curState.dispense()
}

func (s *GumballMachine) setState(st State) {
	s.curState = st
}

func (s *GumballMachine) releaseBall() {
	fmt.Println("A gumball comes rolling out the slot...")
	if s.count > 0 {
		s.count--
	}
}

func (s *GumballMachine) getCount() int {
	return s.count
}

func (s *GumballMachine) getSoldOutState() State {
	return s.soldOutState
}

func (s *GumballMachine) getNoQuarterState() State {
	return s.noQuarterState
}

func (s *GumballMachine) getHasQuarterState() State {
	return s.hasQuarterState
}

func (s *GumballMachine) getSoldState() State {
	return s.soldState
}
